﻿#include	"Engine.Log.hpp"
#include	"Engine.System.hpp"
#include	"Engine.Model.hpp"
#include	"Engine.Thread.hpp"
#include	"Engine.String.hpp"

#include	<cstdarg>
#include	<cstdlib>
#include	<cstring>
#include	<map>

namespace Engine { namespace Log {

	/// @Brief	: 日志文件对象的管理器
	class Manager : public ISingleton<Manager> {
	public:
		Manager();
		virtual ~Manager();

		void		Init(const char * pStrPath, const size_t nMaxSize, const int nLevelSet, bool bEnableConsole);
		Logger &	Get(const char * pStrName);
		FILE *		MakeFile(const System::Time & iCur, const char * szName);

		inline size_t		GetMaxSize() const { return _nMaxSize; }
		inline int			GetWriteLevel() const { return _nLevelSet; }
		inline bool			GetEnableConsole() const { return _bEnableConsole; }
	
	private:
		size_t			_nMaxSize;
		int				_nLevelSet;
		bool			_bEnableConsole;
		std::string		_szRootPath;

		std::map<std::string, Logger *>	_mpAllFiles;
	};

#define		GLoggerMgr()	Manager::Instance()

	Logger::Logger(const char * pStrName)
		: _iLock()
		, _nWrited(0)
		, _pFile(nullptr)
		, _nDay(-1)
		, _nFlush(0)
		, _szName(pStrName) {
	}

	Logger::~Logger() {
		if (_pFile) fclose(_pFile);
	}

	void Logger::Error(const char * pStrFile, const char * pStrFunc, const int nLine, const char * pStrFmt, ...) {
		va_list args;
		va_start(args, pStrFmt);
		_Write(pStrFile, pStrFunc, nLine, ELevel::Error, pStrFmt, "ERR", args);
		va_end(args);
	}

	void Logger::Warn(const char * pStrFile, const char * pStrFunc, const int nLine, const char * pStrFmt, ...) {
		va_list args;
		va_start(args, pStrFmt);
		_Write(pStrFile, pStrFunc, nLine, ELevel::Warn, pStrFmt, "WRN", args);
		va_end(args);
	}

	void Logger::Info(const char * pStrFile, const char * pStrFunc, const int nLine, const char * pStrFmt, ...) {
		va_list args;
		va_start(args, pStrFmt);
		_Write(pStrFile, pStrFunc, nLine, ELevel::Info, pStrFmt, "INF", args);
		va_end(args);
	}

	void Logger::Debug(const char * pStrFile, const char * pStrFunc, const int nLine, const char * pStrFmt, ...) {
		va_list args;
		va_start(args, pStrFmt);
		_Write(pStrFile, pStrFunc, nLine, ELevel::Debug, pStrFmt, "DBG", args);
		va_end(args);
	}

	void Logger::_Write(const char * pStrFile, const char * pStrFunc, const int nLine, ELevel emLvl, const char * pStrFmt, const char * pStrKey, va_list args) {
		Manager & rMgr = GLoggerMgr();

		if (!(emLvl & rMgr.GetWriteLevel())) return;
		std::unique_lock<std::mutex>	iAuto(_iLock);

		System::Time iTime	= System::Time::Current();
		size_t nThreadId	= Thread::Current::GetId();
		
		if (_nDay != iTime.nDay || _nWrited >= rMgr.GetMaxSize()) {
			_pFile		= rMgr.MakeFile(iTime, _szName.c_str());
			_nWrited	= 0;
			_nDay		= iTime.nDay;
		}

		if (!_pFile) return;

		memset(_pBuffer, 0, 1024);

	#if defined(_WIN32) && defined(_MSC_VER)
		sprintf_s(_pBuffer, 1024, "[%04d-%02d-%02d %02d:%02d:%02d.%03d][%08X][%s][%s@%s:%d]",
	#else
		snprintf(_pBuffer, 1024, "[%04d-%02d-%02d %02d:%02d:%02d.%03d][%08X][%s][%s@%s:%d]",
	#endif
			iTime.nYear, iTime.nMonth, iTime.nDay, iTime.nHour, iTime.nMinute, iTime.nSecond, iTime.nMilliSec,
			(unsigned int)nThreadId, pStrKey, pStrFile, pStrFunc, nLine);

	#if defined(_WIN32) && defined(_MSC_VER)
		vsprintf_s(_pBuffer + strlen(_pBuffer), 1024 - strlen(_pBuffer), pStrFmt, args);
	#else
		vsnprintf(_pBuffer + strlen(_pBuffer), 1024 - strlen(_pBuffer), pStrFmt, args);
	#endif

		_nWrited += fprintf(_pFile, "%s\n", _pBuffer);

		if (iTime.nUnixSec - _nFlush > 1) {
			fflush(_pFile);
			_nFlush = iTime.nUnixSec;
		}

		if (rMgr.GetEnableConsole()) printf("%s\n", _pBuffer);
	}

	Manager::Manager()
		: _nMaxSize(1024 * 1024 * 4)
		, _nLevelSet(ELevel::All)
		, _bEnableConsole(true)
		, _szRootPath("Logs") {
	}

	Manager::~Manager() {
		for (auto it = _mpAllFiles.begin(); it != _mpAllFiles.end(); ++it) {
			Logger * pFile = it->second;
			delete pFile;
		}

		_mpAllFiles.clear();
	}

	void Manager::Init(const char * pStrPath, const size_t nMaxSize, const int nLevelSet, bool bEnableConsole) {
		_nMaxSize		= nMaxSize;
		_nLevelSet		= nLevelSet;
		_bEnableConsole	= bEnableConsole;
		_szRootPath		= pStrPath;
	}

	Logger & Manager::Get(const char * pStrName) {		
		auto it	= _mpAllFiles.find(pStrName);
		if (it != _mpAllFiles.end())
			return (* (it->second));

		Logger * pFile = new Logger(pStrName);
		_mpAllFiles[pStrName] = pFile;

		return (* pFile);
	}

	FILE * Manager::MakeFile(const System::Time & iCur, const char * szName) {
		std::string szPath	= String::Format("%s/%04d_%02d_%02d", _szRootPath.c_str(), iCur.nYear, iCur.nMonth, iCur.nDay);
		std::string szFile	= String::Format(
			"%s/%s_%02d_%02d_%02d_%03d.log", 
			szPath.c_str(), szName, iCur.nHour, iCur.nMinute, iCur.nSecond, iCur.nMilliSec);

		if (!System::Path::Exists(_szRootPath)) System::Path::Create(_szRootPath);
		if (!System::Path::Exists(szPath)) System::Path::Create(szPath);

		FILE * pFile = NULL;

	#if defined(_WIN32) && defined(_MSC_VER)
		if (fopen_s(&pFile, szFile.c_str(), "w+") != 0) {
	#else
		if ((pFile = fopen(szFile.c_str(), "w+")) == NULL) {
	#endif
			printf("[%04d-%02d-%02d %02d:%02d:%02d.%03d][%08X][ERR][%s@%s:%d] Can NOT Create Logger file named : %s!!!\n",
				iCur.nYear, iCur.nMonth, iCur.nDay, iCur.nHour, iCur.nMinute, iCur.nSecond, iCur.nMilliSec,
				(unsigned int)Thread::Current::GetId(), __FILE__, __FUNCTION__, __LINE__, szFile.c_str());
		}

		return pFile;
	}

	void Init(const char * pStrPath, const size_t nMaxSize, const int nLevelSet /* = Level::All */, bool bEnableConsole /* = true */) {
		return GLoggerMgr().Init(pStrPath, nMaxSize, nLevelSet, bEnableConsole);
	}

	Logger & Get(const char * pStrName) {
		return GLoggerMgr().Get(pStrName);
	}

}}

